package org.travelfusion.xmlclient.ri.handler.misc;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;

import org.travelfusion.xmlclient.exception.InvalidResponseException;
import org.travelfusion.xmlclient.handler.HandlesRequestsFor;
import org.travelfusion.xmlclient.impl.handler.AbstractXmlToolRequestStAXResponseHandler;
import org.travelfusion.xmlclient.ri.xobject.misc.XAffiliateSupplier;
import org.travelfusion.xmlclient.ri.xobject.misc.XListAffiliateSuppliersRequest;
import org.travelfusion.xmlclient.ri.xobject.misc.XListAffiliateSuppliersResponse;
import org.travelfusion.xmlclient.ri.xobject.misc.XAffiliateSupplier.Feature;
import org.travelfusion.xmlclient.ri.xobject.misc.XAffiliateSupplier.FeatureSellingPointInfo;
import org.travelfusion.xmlclient.ri.xobject.misc.XAffiliateSupplier.FeatureSupportType;
import org.travelfusion.xmlclient.ri.xobject.misc.XAffiliateSupplier.ImplementationType;
import org.travelfusion.xmlclient.ri.xobject.misc.XAffiliateSupplier.Mode;
import org.travelfusion.xmlclient.ri.xobject.misc.XAffiliateSupplier.OpenJawSupport;
import org.travelfusion.xmlclient.ri.xobject.misc.XAffiliateSupplier.PaymentSupport;
import org.travelfusion.xmlclient.ri.xobject.misc.XAffiliateSupplier.ReferralTracker;
import org.travelfusion.xmlclient.ri.xobject.misc.XAffiliateSupplier.ReferralTrackerType;

/**
 * A tool for processing the ListAffiliateSuppliers XML command response
 * @author Veselin Andreev (veselin.andreev@gmail.com)
 */
@HandlesRequestsFor(XListAffiliateSuppliersRequest.class)
public class ListAffiliateSuppliersHandler extends AbstractXmlToolRequestStAXResponseHandler<XListAffiliateSuppliersRequest, XListAffiliateSuppliersResponse> {

	@Override
	protected void fillTemplate() throws Exception {
	}

	/**
	 * A wrapper representing all XML node names used to read the response
	 */
	private static interface NodeList {

		String MODE = "Mode";
		String MODE_NAME = "Name";
		String SUPPLIER_FAMILY_GROUPS = "FamilyGroup";
		String SUPPLIER_FAMILY_GROUP = "Group";
		String SUPPLIER_FAMILY_GROUP_NAME = "Name";
		String SUPPLIER_FAMILY_GROUP_MEMBER = "Supplier";
		String SUPPLIER_FAMILY_GROUP_MEMBER_NAME = "Name";
		String SUPPLIER_LIST = "SupplierList";
		String SUPPLIER = "Supplier";
		String SUPPLIER_NAME = "Name";
		String SUPPLIER_VENDOR_NAME = "VendorName";
		String SUPPLIER_B2B_CHANNEL = "IsB2BChannel";
		String SUPPLIER_STATUS = "Status";
		String SUPPLIER_WEBSITE = "Website";
		String SUPPLIER_VISIBILITY = "IsPublic";
		String SUPPLIER_EXTENSION = "ClassExtension";
		String SUPPLIER_OPENJAWSUPPORT = "OpenJawSupport";
		String SUPPLIER_PAYMENT_TYPES = "PaymentTypeSupport";
		String SUPPLIER_PAYMENT_TYPE_CC = "CreditCard";
		String SUPPLIER_PAYMENT_TYPE_AG = "AgencyPayment";
		String SUPPLIER_PAYMENT_TYPE_TF = "TFPrepay";
		String SUPPLIER_IMPLEMENTATION_TYPE = "StageSupported";
		String SUPPLIER_INTEGRATION_TYPE = "IntegrationType";
		String SUPPLIER_FEATURES = "ParameterList";
		String SUPPLIER_FEATURE = "Parameter";
		String SUPPLIER_FEATURE_NAME = "Name";
		String SUPPLIER_FEATURE_DISPLAY_NAME = "DisplayName";
		String SUPPLIER_FEATURE_IS_DYNAMIC = "IsDynamic";
		String SUPPLIER_FEATURE_IS_PER_PASSENGER = "IsPerPassenger";
		String SUPPLIER_FEATURE_IS_PER_BOOKING = "IsPerBooking";
		String SUPPLIER_FEATURE_DISPLAY_NAME_CUSTOMISABLE = "DisplayNameCustomisable";
		String SUPPLIER_FEATURE_SELLING_POINT_TITLE = "SellingPointTitle";
		String SUPPLIER_FEATURE_SELLING_POINT_DESCRIPTION = "SellingPointDescription";
		String SUPPLIER_DROP_IN_LANGUAGES = "DropInLanguages";
		String SUPPLIER_DROP_IN_LANGUAGE_CODE = "LanguageCode";
		String SUPPLIER_REFERRAL_CLIENTS = "ReferralTrackerClients";
		String SUPPLIER_REFERRAL_CLIENT = "ReferralTrackerClient";
		String SUPPLIER_TRACKER_SYSTEM = "Tracker";
		String SUPPLIER_TRACKER_NAME = "Type";
		String SUPPLIER_TRACKER_TYPE = "Category";
		String SUPPLIER_TRACKER_SUPPORT_CHILD_IDS = "SupportsChildIds";
	}

	/**
	 * A enumeration wrapper representing the current processing event state
	 */
	private static enum EventState {
		/** root node */
		ROOT,
		/** supplier mode type node */
		MODE,
		/** the supplier family groups node */
		SUPPLIER_FAMILY_GROUPS,
		/** the supplier family group node */
		SUPPLIER_FAMILY_GROUP,
		/** supplier list of given mode type node */
		SUPPLIER_LIST,
		/** supplier of given mode type node */
		SUPPLIER,
		/** supplier supported payment types node */
		SUPPLIER_PAYMENT_TYPES,
		/** supplier supported features node */
		SUPPLIER_FEATURES,
		/** supplier supported drop-in languages node */
		SUPPLIER_DROP_IN_LANGUAGES,
		/** supplier supported referral tracker clients node */
		SUPPLIER_REFERRAL_CLIENTS,
		/** supplier referral tracker system node */
		SUPPLIER_REFERRAL_TRACKER
	}

	@Override
	public XListAffiliateSuppliersResponse handleResponse() throws Exception {
		XListAffiliateSuppliersResponse response = new XListAffiliateSuppliersResponse();
		XMLStreamReader reader = getResponseReader();
		StringBuilder buffer = new StringBuilder();
		boolean readBuffer = false;
		EventState state = EventState.ROOT;
		Mode mode = Mode.UNKNOWN;
		XAffiliateSupplier supplier = null;
		String familyGroup = null;
		HashMap<String, String> familyGroups = new HashMap<String, String>();
		List<XAffiliateSupplier> suppliers = new ArrayList<XAffiliateSupplier>();
		for (int event; ((event = reader.next()) != XMLStreamConstants.END_DOCUMENT);) {
			switch (event) {
				case XMLStreamConstants.START_ELEMENT :
					String nodeStart = reader.getName().getLocalPart();
					buffer.setLength(0);
					switch (state) {
						case ROOT :
							if (NodeList.MODE.equals(nodeStart)) {
								state = EventState.MODE;
								if (suppliers.size() != 0) {
									throw new InvalidResponseException("Unexpected event occurred while processing the suppliers information from the last supplier mode");
								}
								if (familyGroups.size() != 0) {
									throw new InvalidResponseException("Unexpected event occurred while processing the family groups information from the last supplier mode");
								}
							}
							break;
						case MODE :
							if (NodeList.SUPPLIER_LIST.equals(nodeStart)) {
								state = EventState.SUPPLIER_LIST;
							} else if (NodeList.SUPPLIER_FAMILY_GROUPS.equals(nodeStart)) {
								state = EventState.SUPPLIER_FAMILY_GROUPS;
							} else if (NodeList.MODE_NAME.equals(nodeStart)) {
								readBuffer = true;
							}
							break;
						case SUPPLIER_FAMILY_GROUPS :
							if (NodeList.SUPPLIER_FAMILY_GROUP.equals(nodeStart)) {
								state = EventState.SUPPLIER_FAMILY_GROUP;
								int attributes = reader.getAttributeCount();
								if (familyGroup != null) {
									throw new InvalidResponseException("Unexpected event occurred while processing the supplier family groups information");
								}
								for (int a = 0; a < attributes; a++) {
									String name = reader.getAttributeName(a).getLocalPart();
									String value = reader.getAttributeValue(a);
									if (NodeList.SUPPLIER_FAMILY_GROUP_NAME.equals(name)) {
										familyGroup = value;
										break;
									}
								}
								if (familyGroup == null) {
									throw new InvalidResponseException("Unexpected event occurred while accessing the supplier family group name");
								}
								familyGroups.put(familyGroup, familyGroup);
							}
							break;
						case SUPPLIER_FAMILY_GROUP :
							if (NodeList.SUPPLIER_FAMILY_GROUP_MEMBER.equals(nodeStart)) {
								int attributes = reader.getAttributeCount();
								String supplierName = null;
								for (int a = 0; a < attributes; a++) {
									String name = reader.getAttributeName(a).getLocalPart();
									String value = reader.getAttributeValue(a);
									if (NodeList.SUPPLIER_FAMILY_GROUP_MEMBER_NAME.equals(name)) {
										supplierName = value;
										break;
									}
								}
								if (supplierName == null) {
									throw new InvalidResponseException("Unexpected event occurred while accessing the supplier family group member name");
								}
								familyGroups.put(supplierName, familyGroup);
							}
							break;
						case SUPPLIER_LIST :
							if (NodeList.SUPPLIER.equals(nodeStart)) {
								if (supplier != null) {
									throw new InvalidResponseException("Unexpected event occurred while processing the supplier information");
								}
								supplier = new XAffiliateSupplier();
								supplier.setMode(mode);
								supplier.setExtension(false);
								state = EventState.SUPPLIER;
							}
							break;
						case SUPPLIER :
							if (NodeList.SUPPLIER_NAME.equals(nodeStart) || NodeList.SUPPLIER_VENDOR_NAME.equals(nodeStart) || NodeList.SUPPLIER_B2B_CHANNEL.equals(nodeStart)
									|| NodeList.SUPPLIER_STATUS.equals(nodeStart) || NodeList.SUPPLIER_WEBSITE.equals(nodeStart) || NodeList.SUPPLIER_VISIBILITY.equals(nodeStart)
									|| NodeList.SUPPLIER_OPENJAWSUPPORT.equals(nodeStart) || NodeList.SUPPLIER_IMPLEMENTATION_TYPE.equals(nodeStart)
									|| NodeList.SUPPLIER_INTEGRATION_TYPE.equals(nodeStart)) {
								readBuffer = true;
							} else if (NodeList.SUPPLIER_EXTENSION.equals(nodeStart)) {
								supplier.setExtension(true);
							} else if (NodeList.SUPPLIER_PAYMENT_TYPES.equals(nodeStart)) {
								state = EventState.SUPPLIER_PAYMENT_TYPES;
							} else if (NodeList.SUPPLIER_FEATURES.equals(nodeStart)) {
								state = EventState.SUPPLIER_FEATURES;
							} else if (NodeList.SUPPLIER_DROP_IN_LANGUAGES.equals(nodeStart)) {
								state = EventState.SUPPLIER_DROP_IN_LANGUAGES;
							} else if (NodeList.SUPPLIER_REFERRAL_CLIENTS.equals(nodeStart)) {
								state = EventState.SUPPLIER_REFERRAL_CLIENTS;
							} else if (NodeList.SUPPLIER_TRACKER_SYSTEM.equals(nodeStart)) {
								state = EventState.SUPPLIER_REFERRAL_TRACKER;
								supplier.setTracker(new ReferralTracker());
							}
							break;
						case SUPPLIER_PAYMENT_TYPES :
							readBuffer = true;
							break;
						case SUPPLIER_FEATURES :
							if (NodeList.SUPPLIER_FEATURE.equals(nodeStart)) {
								Feature feature = new Feature();
								int attributes = reader.getAttributeCount();
								String sellingPointTitle = null, sellingPointDescription = null;
								for (int a = 0; a < attributes; a++) {
									String name = reader.getAttributeName(a).getLocalPart();
									String value = reader.getAttributeValue(a);
									if (NodeList.SUPPLIER_FEATURE_NAME.equals(name)) {
										feature.setName(value);
									} else if (NodeList.SUPPLIER_FEATURE_DISPLAY_NAME.equals(name)) {
										feature.setDisplayName(value);
									} else if (NodeList.SUPPLIER_FEATURE_IS_DYNAMIC.equals(name)) {
										feature.setDynamic(Boolean.parseBoolean(value));
									} else if (NodeList.SUPPLIER_FEATURE_IS_PER_PASSENGER.equals(name) && Boolean.parseBoolean(value)) {
										feature.setSupportType(FeatureSupportType.PER_PASSENGER);
									} else if (NodeList.SUPPLIER_FEATURE_IS_PER_BOOKING.equals(name) && Boolean.parseBoolean(value)) {
										feature.setSupportType(FeatureSupportType.PER_BOOKING);
									} else if (NodeList.SUPPLIER_FEATURE_DISPLAY_NAME_CUSTOMISABLE.equals(name)) {
										feature.setDisplayNameCustomisable(Boolean.parseBoolean(value));
									} else if (NodeList.SUPPLIER_FEATURE_SELLING_POINT_TITLE.equals(name)) {
										sellingPointTitle = new String(value);
									} else if (NodeList.SUPPLIER_FEATURE_SELLING_POINT_DESCRIPTION.equals(name)) {
										sellingPointDescription = new String(value);
									}
								}
								if (feature.getName() == null) {
									throw new InvalidResponseException("Unexpected event occurred while processing the supplier feature information");
								}
								if (sellingPointTitle != null && sellingPointDescription != null) {
									feature.setSellingPoint(new FeatureSellingPointInfo(sellingPointTitle, sellingPointDescription));
								}
								supplier.getFeatures().add(feature);
							}
							break;
						case SUPPLIER_DROP_IN_LANGUAGES :
							if (NodeList.SUPPLIER_DROP_IN_LANGUAGE_CODE.equals(nodeStart)) {
								readBuffer = true;
							}
							break;
						case SUPPLIER_REFERRAL_CLIENTS :
							if (NodeList.SUPPLIER_REFERRAL_CLIENT.equals(nodeStart)) {
								readBuffer = true;
							}
							break;
						case SUPPLIER_REFERRAL_TRACKER :
							if (NodeList.SUPPLIER_TRACKER_NAME.equals(nodeStart) || NodeList.SUPPLIER_TRACKER_TYPE.equals(nodeStart) || NodeList.SUPPLIER_TRACKER_SUPPORT_CHILD_IDS.equals(nodeStart)) {
								readBuffer = true;
							}
							break;
					}
					break;
				case XMLStreamConstants.CHARACTERS :
					if (readBuffer) {
						buffer.append(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength());
					}
					break;
				case XMLStreamConstants.END_ELEMENT :
					String nodeEnd = reader.getName().getLocalPart();
					switch (state) {
						case MODE :
							if (NodeList.MODE.equals(nodeEnd)) {
								state = EventState.ROOT;
								for (XAffiliateSupplier affiliateSupplier : suppliers) {
									String family = familyGroups.get(affiliateSupplier.getName());
									if (family != null) {
										affiliateSupplier.setFamilyName(family);
									}
								}
								response.getSuppliers().addAll(suppliers);
								suppliers.clear();
								familyGroups.clear();
							} else if (NodeList.MODE_NAME.equals(nodeEnd)) {
								readBuffer = false;
								String modeBuffer = buffer.toString().toLowerCase();
								if (modeBuffer.equals("plane")) {
									mode = Mode.PLANE;
								} else if (modeBuffer.equals("hotel")) {
									mode = Mode.HOTEL;
								} else if (modeBuffer.equals("car")) {
									mode = Mode.CAR;
								} else if (modeBuffer.equals("train")) {
									mode = Mode.TRAIN;
								} else if (modeBuffer.equals("ferry")) {
									mode = Mode.FERRY;
								} else if (modeBuffer.equals("prepackaged")) {
									mode = Mode.PREPACKAGED;
								} else {
									mode = Mode.UNKNOWN;
								}
							}
							break;
						case SUPPLIER_FAMILY_GROUPS :
							if (NodeList.SUPPLIER_FAMILY_GROUPS.equals(nodeEnd)) {
								state = EventState.MODE;
							}
							break;
						case SUPPLIER_FAMILY_GROUP :
							if (NodeList.SUPPLIER_FAMILY_GROUP.equals(nodeEnd)) {
								state = EventState.SUPPLIER_FAMILY_GROUPS;
								familyGroup = null;
							}
							break;
						case SUPPLIER_LIST :
							if (NodeList.SUPPLIER_LIST.equals(nodeEnd)) {
								state = EventState.MODE;
							}
							break;
						case SUPPLIER :
							if (NodeList.SUPPLIER.equals(nodeEnd)) {
								state = EventState.SUPPLIER_LIST;
								if (supplier.getName() == null) {
									throw new InvalidResponseException("Unexpected event occurred while gathering the supplier information");
								}
								suppliers.add(supplier);
								supplier = null;
							} else {
								readBuffer = false;
								String valueBuffer = buffer.toString();
								if (supplier == null) {
									throw new InvalidResponseException("Unexpected event occurred while accessing the supplier information");
								}
								if (NodeList.SUPPLIER_NAME.equals(nodeEnd)) {
									supplier.setName(valueBuffer);
								} else if (NodeList.SUPPLIER_VENDOR_NAME.equals(nodeEnd)) {
									supplier.setVendorName(valueBuffer);
								} else if (NodeList.SUPPLIER_B2B_CHANNEL.equals(nodeEnd)) {
									supplier.setB2bChannel(Boolean.parseBoolean(valueBuffer));
								} else if (NodeList.SUPPLIER_STATUS.equals(nodeEnd)) {
									supplier.setEnabled(valueBuffer.toLowerCase().equals("active"));
								} else if (NodeList.SUPPLIER_WEBSITE.equals(nodeEnd)) {
									supplier.setWebsiteUrl(valueBuffer);
								} else if (NodeList.SUPPLIER_VISIBILITY.equals(nodeEnd)) {
									supplier.setVisible(Boolean.parseBoolean(valueBuffer));
								} else if (NodeList.SUPPLIER_OPENJAWSUPPORT.equals(nodeEnd)) {
									if (valueBuffer.equals("Supported")) {
										supplier.setOpenJaw(OpenJawSupport.SUPPORTED);
									} else if (valueBuffer.equals("Supported with city search")) {
										supplier.setOpenJaw(OpenJawSupport.SUPPORTED_WITH_CITY_SEARCH);
									} else if (valueBuffer.equals("Not Supported")) {
										supplier.setOpenJaw(OpenJawSupport.NOT_SUPPORTED);
									}
								} else if (NodeList.SUPPLIER_IMPLEMENTATION_TYPE.equals(nodeEnd)) {
									String[] blockInfo = valueBuffer.toLowerCase().split("_");
									if (blockInfo.length == 3) {
										if ("route".equals(blockInfo[1])) {
											supplier.setType(ImplementationType.ROUTE_ONLY);
										} else if ("search".equals(blockInfo[1])) {
											supplier.setType(ImplementationType.SEARCH_ONLY);
										} else if ("book".equals(blockInfo[1])) {
											supplier.setType(ImplementationType.BOOK_ONLY);
										}
									}
								} else if (NodeList.SUPPLIER_INTEGRATION_TYPE.equals(nodeEnd)) {
									supplier.parseIntegrationType(valueBuffer);
								}
							}
							break;
						case SUPPLIER_PAYMENT_TYPES :
							if (NodeList.SUPPLIER_PAYMENT_TYPES.equals(nodeEnd)) {
								state = EventState.SUPPLIER;
							} else {
								readBuffer = false;
								boolean supported = Boolean.parseBoolean(buffer.toString());
								if (supported) {
									if (NodeList.SUPPLIER_PAYMENT_TYPE_CC.equals(nodeEnd)) {
										supplier.addPaymentSupport(PaymentSupport.CREDIT_CARD);
									} else if (NodeList.SUPPLIER_PAYMENT_TYPE_AG.equals(nodeEnd)) {
										supplier.addPaymentSupport(PaymentSupport.AGENCY_PAYMENT);
									} else if (NodeList.SUPPLIER_PAYMENT_TYPE_TF.equals(nodeEnd)) {
										supplier.addPaymentSupport(PaymentSupport.TRAVELFUSION_CREDIT_SETTLEMENT_PLAN);
									}
								}
							}
							break;
						case SUPPLIER_FEATURES :
							if (NodeList.SUPPLIER_FEATURES.equals(nodeEnd)) {
								state = EventState.SUPPLIER;
							}
							break;
						case SUPPLIER_DROP_IN_LANGUAGES :
							if (NodeList.SUPPLIER_DROP_IN_LANGUAGES.equals(nodeEnd)) {
								state = EventState.SUPPLIER;
							} else if (NodeList.SUPPLIER_DROP_IN_LANGUAGE_CODE.equals(nodeEnd)) {
								readBuffer = false;
								String code = buffer.toString();
								if (code.length() != 2) {
									throw new InvalidResponseException("Unexpected event occurred while processing the supplier drop-in language information");
								}
								supplier.getDropInLanguages().add(new String(code));
							}
							break;
						case SUPPLIER_REFERRAL_CLIENTS :
							if (NodeList.SUPPLIER_REFERRAL_CLIENTS.equals(nodeEnd)) {
								state = EventState.SUPPLIER;
							} else if (NodeList.SUPPLIER_REFERRAL_CLIENT.equals(nodeEnd)) {
								readBuffer = false;
								String client = buffer.toString();
								supplier.getTrackerClients().add(new String(client));
							}
							break;
						case SUPPLIER_REFERRAL_TRACKER :
							if (NodeList.SUPPLIER_TRACKER_SYSTEM.equals(nodeEnd)) {
								state = EventState.SUPPLIER;
								ReferralTracker tracker = supplier.getTracker();
								if (tracker == null || tracker.getName() == null) {
									throw new InvalidResponseException("Unexpected event occurred while processing the supplier referral tracker system information");
								}
								if (tracker.getName().toLowerCase().equals("none")) {
									supplier.clearTracker();
								}
							} else if (NodeList.SUPPLIER_TRACKER_NAME.equals(nodeEnd) || NodeList.SUPPLIER_TRACKER_TYPE.equals(nodeEnd) || NodeList.SUPPLIER_TRACKER_SUPPORT_CHILD_IDS.equals(nodeEnd)) {
								readBuffer = false;
								String data = buffer.toString();
								if (NodeList.SUPPLIER_TRACKER_NAME.equals(nodeEnd)) {
									supplier.getTracker().setTrackerName(data);
								} else if (NodeList.SUPPLIER_TRACKER_TYPE.equals(nodeEnd)) {
									if (data.equals("T")) {
										supplier.getTracker().setTrackerType(ReferralTrackerType.THIRD_PARTY_TRACKER);
									} else if (data.equals("S")) {
										supplier.getTracker().setTrackerType(ReferralTrackerType.SUPPLIER_OWN_TRACKER);
									} else if (data.equals("F")) {
										supplier.getTracker().setTrackerType(ReferralTrackerType.TRAVELFUSION_TRACKER);
									} else {
										supplier.getTracker().setTrackerType(ReferralTrackerType.UNKNOWN);
									}
								} else if (NodeList.SUPPLIER_TRACKER_SUPPORT_CHILD_IDS.equals(nodeEnd)) {
									supplier.getTracker().setChildIdsSupport(Boolean.parseBoolean(data));
								}
							}
							break;
					}
					break;
			}
		}
		return response;
	}
}